﻿# coding: utf-8

import Engine
import Entity
import EntityModel
import EntityTerrain
import EntityTerrainRegion
import EntityParticleSystem
import EntityWater
import SceneNode
import SceneManager
import Camera
import Math
import Painter
import RefCounter
import RenderDevice
import ResourceManager
import RenderDefine
import TerrainRegion

import weakref

from InputHandler import CInputHandler


_LAYER_TEX_NAME_PREFIX = 'texLayer'
_LAYER_BLEND_TEX_NAME = 'texBlendWeight'
_LIGHT_MAP_TEX_NAME = 'texLightMap'
_SHADOW_MAP_SIZE = 512
_BLEND_MAP_SIZE = 32

class CSceneNode(HANDLE):
    def __init__(self, coreHandle, parent):
        HANDLE.__init__(self, coreHandle)

        if parent:
            self._parentRef = weakref.ref(parent)
        else:
            self._parentRef = None

        self._children = []

        # hierarchy panel data.
        self._treeItem = None
        
        # model data.
        self._fileName = ''
        
        # water data.
        self._waterGridSize = 0
        self._waterWidth = 0
        self._waterHeight = 0
        self._waterShaderFile = ''
        self._waterBumpTextureFile = ''
        
    def GetName(self):
        return SceneNode.GetName(self.GetCoreHandle())
        
    def SetName(self, name):
        SceneNode.SetName(self.GetCoreHandle() ,name)

class CSceneEditor(CInputHandler):
    """ Scene editor used to edit the whole scene """
    
    __ID = 0

    def __init__(self):
        """ Default class constructor """
        
        CInputHandler.__init__(self)

        # Root scene node
        self._root = CSceneNode(0, None)

        # Orphance scene node list
        self._orphanceSceneNodeList = []
        
        # The position of mouse left down
        self._mouseLeftDownPos = None
        
        # The current position of the mouse
        self._mouseCurrentPos = None
        
        # Selected object list
        self._selectedObjectRefList = []
        
    def GetID(self):
        CSceneEditor.__ID += 1
        return CSceneEditor.__ID
        
    def Create(self):
        """ Create the scene editor """

        # Create camera.
        self._mainCameraHandle = Camera._New()
        self.ResetCamera()
        Engine.SetCurrentCamera(self._mainCameraHandle)            
        
        # Create scene.
        sceneHandle = SceneManager.CreateScene('WorldEditorScene')
        self._root.SetCoreHandle(sceneHandle)
        self._root._treeItem = PANEL_HIERARCHY._root
        
        # Refresh the hierarchy panel.
        PANEL_HIERARCHY.OnRefreshSceneNode(self._root)
        
    def Destroy(self):
        """ Destroy the scene editor """
        
        for node in self._orphanceSceneNodeList[:]:
            self.RemoveSceneNode(node)
            
        RefCounter.Release(Camera._CRefCounter(self._mainCameraHandle))
        RefCounter.Release(SceneNode._CRefCounter(self._root.GetCoreHandle()))

    def ResetCamera(self):
        """ Set camera parameters to default """
        
        if not self._mainCameraHandle:
            return
            
        Camera.SetPosition(self._mainCameraHandle, 0, 400, -300)
        Camera.SetLookDirection(self._mainCameraHandle, 0, -1, 1)
        Camera.SetUpDirection(self._mainCameraHandle, 0, 1, 0)
        Camera.SetViewFar(self._mainCameraHandle, 10000.0)
                    
    def __RenderSceneNode(self, sceneNode):
        """ Render all entities attached to the scene """
        
        sceneNodeHandle = sceneNode.GetCoreHandle()
        entityCount = SceneNode.GetEntityCount(sceneNodeHandle)
        for i in xrange(0, entityCount):
            entityHandle = SceneNode.GetEntity(sceneNodeHandle, i)
            Entity.Render(entityHandle)
                    
    def __RenderSceneNodeAABB(self, sceneNode):
        """ Render world AABB of all the entities that attached to the scene node """
        
        sceneNodeHandle = sceneNode.GetCoreHandle()
        entityCount = SceneNode.GetEntityCount(sceneNodeHandle)
        for i in xrange(0, entityCount):
            entityHandle = SceneNode.GetEntity(sceneNodeHandle, i)
            entityType = Entity.GetType(entityHandle)
            if entityType in [Entity.ET_TERRAIN, Entity.ET_TERRAIN_REGION, Entity.ET_SKY_BOX]:
                continue
                
            minX, minY, minZ, maxX, maxY, maxZ = Entity.GetWorldAABB(entityHandle)
            Painter.DrawAABB(minX, minY, minZ, maxX, maxY, maxZ, 0xffffffff)
        
    def Render(self):
        """ Render all nodes """

        for node in self._orphanceSceneNodeList:
            self.__RenderSceneNode(node)
            
        # Draw all AABBs of selected objects
        for ref in self._selectedObjectRefList:
            node = ref()
            if not node:
                continue
            
            self.__RenderSceneNodeAABB(node)    
         
        # Draw select box
        Painter.Flush()
        RenderDevice.Clear(0, 1, 1, 0xff000000, 1.0, 0)
        if self._mouseLeftDownPos and self._mouseCurrentPos:
            x1, y1 = self._mouseLeftDownPos
            x2, y2 = self._mouseCurrentPos
            if x1 != x2 or y1 != y2:
                Painter.DrawLine2D(x1, y1, x2, y1, 0xff00ff00, 0xff00ff00)
                Painter.DrawLine2D(x2, y1, x2, y2, 0xff00ff00, 0xff00ff00)
                Painter.DrawLine2D(x1, y1, x1, y2, 0xff00ff00, 0xff00ff00)
                Painter.DrawLine2D(x1, y2, x2, y2, 0xff00ff00, 0xff00ff00)
            
    def Update(self, dt):
        """
        Update all
        
        Parameters:
        @dt - delta time between 2 frame
        """
        
        for node in self._orphanceSceneNodeList:
            SceneNode.Update(node.GetCoreHandle(), dt, False)

    def CreateModelSceneNode(self, fileName, addToSceneGraph):
        """
        Create a model scene node

        Parameters:
        @param fileName - the model's relative file path
        @param addToSceneGraph - whether add the new node to scene graph
        @return - (weak ref of the scene node, entity core handle)
        """

        # Create model scene node.
        node = self.__CreateEmptySceneNode(addToSceneGraph)
        node._fileName = fileName
        node.SetName('Model %d' % self.GetID())

        # Load model from file
        entityHandle = EntityModel._New(self._root.GetCoreHandle())
        if not EntityModel.LoadFromFile(entityHandle, fileName):
            EntityModel._Delete(entityHandle)
            SceneNode._Delete(node.GetCoreHandle())
            return None, None

        # Attach model entity.
        SceneNode.AttachEntity(node.GetCoreHandle(), entityHandle)
        
        # Add to scene editor
        self._AddSceneNode(node)

        return weakref.ref(node), entityHandle
        
    def CreateTerrainSceneNode(self, width, height, shaderFile, defaultLayerFile):
        """
        Create a terrain scene node
        
        Parameters:
        @param width - the region width of the terrain
        @param height - the region height of the terrain
        @param shaderFile - the shader file used by the terrain scene node
        @param defaultLayerFile - the texture file used for the first terrain layer
        @return - (weak ref of the scene node, entity core handle)
        """
        
        # Create the scene node.
        node = self.__CreateEmptySceneNode(True)
        node.SetName('Terrain %d' % self.GetID())
        
        # Create the terrain entity.
        entityHandle = EntityTerrain._New(self._root.GetCoreHandle())
        EntityTerrain.Create(entityHandle, width, height)
        
        try:
            terrainRegionHandle = None
            texBlend = None
            texLightMap = None            

            for x in xrange(0, width):
                for y in xrange(0, height):
                    ret, terrainRegionHandle = EntityTerrain.GetTerrainRegionByIndex(entityHandle, x, y)
                    assert(ret)
                        
                    ret = EntityTerrainRegion.Create(terrainRegionHandle, x, y, shaderFile)
                    assert(ret)
                        
                    ret, texBlend = ResourceManager.GetTexture2D(_BLEND_MAP_SIZE, _BLEND_MAP_SIZE, RenderDefine.PF_A8R8G8B8, 1, RenderDefine.TU_NORMAL)
                    assert(ret)
                    
                    ret = EntityTerrainRegion.AddTextureMap(terrainRegionHandle, _LAYER_BLEND_TEX_NAME, texBlend, 0)
                    assert(ret)
                                            
                    ret = EntityTerrainRegion.FillTextureMapA8R8G8B8(terrainRegionHandle, _LAYER_BLEND_TEX_NAME, 0x00ff0000)
                    assert(ret)
                        
                    RefCounter.Release(texBlend)
                    texBlend = None
                        
                    ret, texLightMap = ResourceManager.GetTexture2D(_SHADOW_MAP_SIZE, _SHADOW_MAP_SIZE, RenderDefine.PF_L8, 1, RenderDefine.TU_NORMAL)
                    assert(ret)
                        
                    ret = EntityTerrainRegion.AddTextureMap(terrainRegionHandle, _LIGHT_MAP_TEX_NAME, texLightMap, 0)
                    assert(ret)
                        
                    ret = EntityTerrainRegion.FillTextureMapL8(terrainRegionHandle, _LIGHT_MAP_TEX_NAME, 255)
                    assert(ret)
                        
                    RefCounter.Release(texLightMap)
                    texLightMap = None
                    
                    ret = EntityTerrainRegion.AddTextureMapFromFile(terrainRegionHandle, '%s1' % _LAYER_TEX_NAME_PREFIX , defaultLayerFile, 1)
                    assert(ret)
                    
        except:
            SHOW_TRACE()
            
            if texLightMap:
                RefCounter.Release(texLightMap)
               
            if texBlend:
                RefCounter.Release(texBlend)
                
            EntityTerrain._Delete(entityHandle)
            SceneNode._Delete(node.GetCoreHandle())
            return None, None
            
        EntityTerrain.LinkAllRegion(entityHandle);
        EntityTerrain.RecalculateNormal(entityHandle);
        EntityTerrain.UpdatePositionVertexBuffer(entityHandle);
        EntityTerrain.UpdateNormalVertexBuffer(entityHandle);
        EntityTerrain.UpdateTextureCoordVertexBuffer(entityHandle);
                    
        # Attach model entity.
        SceneNode.AttachEntity(node.GetCoreHandle(), entityHandle)
        
        # Add to scene editor
        self._AddSceneNode(node)
                            
        return weakref.ref(node), entityHandle
        
    def CreateParticleSystemSceneNode(self, fileName, addToSceneGraph):
        """
        Create a particle system scene node

        Parameters:
        @param fileName - the particle system relative file path, if this is None, then create a new one.
        @param addToSceneGraph - whether add the new node to scene graph
        """        
        
        node = self.__CreateEmptySceneNode(addToSceneGraph)
        node.fileName = fileName
        node.SetName('ParticleSystem %d' % SCENE_EDITOR.GetID())

        # Create a particle system entity.
        entityHandle = EntityParticleSystem._New(self._root.GetCoreHandle())

        # Attach this entity to scene node.
        SceneNode.AttachEntity(sceneNodeHandle, entityHandle)
        
        # Add to scene editor
        self._AddSceneNode(node)
        
        return weakref.ref(node), entityHandle
    
    def CreateWaterSceneNode(self, gridSize, width, height, shaderFile, bumpTextureFile, addToSceneGraph):
        """
        Create a water region scene node
        
        Parameters:
        @param gridSize: grid size of the water region mesh.
        @param width: width of the water region mesh.
        @param height: height of the water region mesh.
        @param shaderFile: the shader file name which used for the water rendering.
        @param bumpTextureFile: the bump texture used for water rendering.
        @param addToSceneGraph: whether add the new scene node to scene graph.
        """
        
        node = self.__CreateEmptySceneNode(addToSceneGraph)
        node._waterGridSize = gridSize
        node._waterWidth = width
        node._waterHeight = height
        node._waterShaderFile = shaderFile
        node._waterBumpTextureFile = bumpTextureFile
        node.SetName('WaterRegion %d' % SCENE_EDITOR.GetID())
        
        # Create a water region entity.
        entityHandle = EntityWater._New(self._root.GetCoreHandle())
        if not EntityWater.Create(entityHandle, width, height, gridSize, shaderFile):
            return None, None
        
        if not EntityWater.CreateBumpTexture(entityHandle, bumpTextureFile):
            return None, None
        
        # Attach this entity to scene node.
        SceneNode.AttachEntity(node.GetCoreHandle(), entityHandle)
        
        # Add to scene editor.
        self._AddSceneNode(node)
        
        return weakref.ref(node), entityHandle
        
    def __CreateEmptySceneNode(self, addToSceneGraph):
        """ Create a empty scene node """
        
        if addToSceneGraph:
            sceneNodeHandle = SceneNode._New(self._root.GetCoreHandle())
            node = CSceneNode(sceneNodeHandle, self._root)
        else:
            sceneNodeHandle = SceneNode._New(0)
            node = CSceneNode(sceneNodeHandle, None)
            
        return node
                    
    def RemoveSceneNode(self, node):
        """ Delete and remove a scene node from scene graph """

        self.__RemoveSceneNode(node)

    def _AddSceneNode(self, node):
        """ Add a scene node to scene graph """

        # If the node's parent is None, then it's orphance node.
        if not node._parentRef:
            self._orphanceSceneNodeList.append(node)
        else:
            # Add the new scene node to root.
            parent = node._parentRef()
            if parent:
                parent._children.append(node)
                SceneNode.AddChild(parent.GetCoreHandle(), node.GetCoreHandle())
                RefCounter.Release(SceneNode._CRefCounter(node.GetCoreHandle()))
    
                # Refresh the hierarchy tree.
                PANEL_HIERARCHY.OnRefreshSceneNode(parent)

    def __RemoveSceneNode(self, node):
        """ Remove a scene node from scene graph """

        # If the node's parent is None, then it's orphance node.
        if not node._parentRef:
            self._orphanceSceneNodeList.remove(node)
            SceneNode._Delete(node.GetCoreHandle())
        else:
            # Remove the scene node from scene graph
            parent = node._parentRef()
            if parent:
                parent._children.remove(node)
                SceneNode.RemoveChild(parent.GetCoreHandle(), node.GetCoreHandle())

                # Refresh the hierarchy tree.
                PANEL_HIERARCHY.OnRefreshSceneNode(parent)
                     
    def OnMouseLeftDown(self, e):
        """ Process mouse left down message """
        self._mouseLeftDownPos = e.GetPosition()
           
    def OnMouseLeftUp(self, e):
        """ Process mouse up down message """
        
        if not self._mouseLeftDownPos:
            return
            
        x, y = e.GetPosition()
        downX, downY = self._mouseLeftDownPos
        
        # One object pick or group pick
        if x == downX and y == downY:
            self.__PickOneObject(x, y, e.ControlDown())
        else:
            self.__PickGroupObject(downX, downY, x, y)
            
        self._mouseLeftDownPos = None
        self._mouseCurrentPos = None
            
    def OnMouseMotion(self, e):
        """ Process mouse move message """
        
        if not self._mouseLeftDownPos:
            return
            
        x, y = e.GetPosition()
        self._mouseCurrentPos = (x, y)
        
        
    def __PickOneObject(self, x, y, ctrlDown):
        """ Pick one object from a point """
        
        width, height = PANEL_EDITOR_SCENE.GetSizeTuple()
        x0, y0, z0, nx, ny, nz = Camera.GetPickRay(Engine.GetCurrentCamera(), x, y, width, height)
        param = {'dir' : (x0, y0, z0, nx, ny, nz), 'selected' : None}
        
        # Define callback function to process every node
        def __CallBackPickOneObject(node, param):
            x0, y0, z0, nx, ny, nz = param['dir']
            selected = param['selected']
            sceneNodeHandle = node.GetCoreHandle()
            entityCount = SceneNode.GetEntityCount(sceneNodeHandle)
            for i in xrange(0, entityCount):
                entityHandle = SceneNode.GetEntity(sceneNodeHandle, i)
                minX, minY, minZ, maxX, maxY, maxZ = Entity.GetWorldAABB(entityHandle)
                colide, x, y, z = Math.AABBCollisionWithRay(minX, minY, minZ, maxX, maxY, maxZ, x0, y0, z0, nx, ny, nz)
                if colide:
                    if selected == None:
                        param['selected'] = (node, x, y, z)
                    else:
                        n, x1, y1, z1 = selected
                        dist1 = (x0 - x1) * (x0 - x1) + (y0 - y1) * (y0 - y1) + (z0 - z1) * (z0 - z1)
                        dist2 = (x0 - x) * (x0 - x) + (y0 - y) * (y0 - y) + (z0 - z) * (z0 - z)
                        if dist1 > dist2:
                            param['selected'] = (node, x, y, z)		
                break                
            
        self.TraceAllSceneNode(__CallBackPickOneObject, param)
        
        selected = param['selected']
        if selected:
            node, x, y, z = selected
            ref = weakref.ref(node)
            if ctrlDown:
                if ref in self._selectedObjectRefList:
                    self._selectedObjectRefList.remove(ref)
                else:
                    self._selectedObjectRefList.append(ref)
            else:
                self._selectedObjectRefList = [weakref.ref(node)]
            PANEL_HIERARCHY.RefreshSelectedSceneNodeList()
        else:
            self._selectedObjectRefList = []
        
    def TraceAllSceneNode(self, cb, param):
        """ Trace all scene node """
        
        def __TraceAllSceneNode(node, cb, param):
            cb(node, param)
            for child in node._children:
                __TraceAllSceneNode(child, cb, param)

        __TraceAllSceneNode(self._root, cb, param)
        
    def __PickGroupObject(self, startX, startY, endX, endY):
        """ Pick a group of object from a rect """
        
        pass
        
    
                      

__builtins__['SCENE_EDITOR'] = CSceneEditor()